/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.l2j.gameserver.instancemanager;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.logging.Logger;

import javolution.util.FastList;
import net.sf.l2j.database.L2DatabaseFactory;
import net.sf.l2j.gameserver.ThreadPoolManager;
import net.sf.l2j.gameserver.datatables.NpcTable;
import net.sf.l2j.gameserver.idfactory.IdFactory;
import net.sf.l2j.gameserver.model.AutoChatHandler;
import net.sf.l2j.gameserver.model.L2ItemInstance;
import net.sf.l2j.gameserver.model.L2World;
import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
import net.sf.l2j.gameserver.model.actor.instance.L2SiegeGuardInstance;
import net.sf.l2j.gameserver.model.entity.Castle;
import net.sf.l2j.gameserver.templates.L2NpcTemplate;

/**
 * @author yellowperil & Fulminus This class is similar to the
 *         SiegeGuardManager, except it handles the loading of the mercenary
 *         tickets that are dropped on castle floors by the castle lords. These
 *         tickets (aka badges) need to be readded after each server reboot
 *         except when the server crashed in the middle of an ongoig siege. In
 *         addition, this class keeps track of the added tickets, in order to
 *         properly limit the number of mercenaries in each castle and the
 *         number of mercenaries from each mercenary type. Finally, we provide
 *         auxilary functions to identify the castle in which each item (and its
 *         corresponding NPC) belong to, in order to help avoid mixing them up.
 */
public class MercTicketManager {
	protected static Logger _log = Logger.getLogger(CastleManager.class
			.getName());

	public static MercTicketManager getInstance() {
		return SingletonHolder._instance;
	}

	// =========================================================
	// =========================================================
	// Data Field
	private List<L2ItemInstance> _droppedTickets; // to keep track of items on
													// the ground
	// TODO move all these values into siege.properties
	// max tickets per merc type = 10 + (castleid * 2)?
	// max ticker per castle = 40 + (castleid * 20)?
	private static final int[] MAX_MERC_PER_TYPE = { 10, 10, 10, 10, 10, 10,
			10, 10, 10, 10, 10, 10,
			10,
			10,
			10,
			10,
			10,
			10,
			10,
			10, // Gludio
			15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
			15,
			15,
			15,
			15,
			15,
			15,
			15,
			15, // Dion
			10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
			10,
			10,
			10,
			10,
			10,
			10,
			10, // Giran
			10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
			10,
			10,
			10,
			10,
			10,
			10, // Oren
			20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
			20,
			20,
			20,
			20,
			20, // Aden
			20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
			20,
			20,
			20,
			20, // Innadril
			20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
			20,
			20,
			20, // Goddard
			20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
			20, 20,
			20, // Rune
			20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
			20, 20, 20 // Schuttgart
	};

	private static final int[] MERCS_MAX_PER_CASTLE = { 100, // Gludio
			150, // Dion
			200, // Giran
			300, // Oren
			400, // Aden
			400, // Innadril
			400, // Goddard
			400, // Rune
			400 // Schuttgart
	};

	private static final int[] ITEM_IDS = { 3960, 3961, 3962, 3963, 3964, 3965,
			3966, 3967, 3968, 3969, 6038,
			6039,
			6040,
			6041,
			6042,
			6043,
			6044,
			6045,
			6046,
			6047, // Gludio
			3973, 3974, 3975, 3976, 3977, 3978, 3979, 3980, 3981, 3982, 6051,
			6052, 6053,
			6054,
			6055,
			6056,
			6057,
			6058,
			6059,
			6060, // Dion
			3986, 3987, 3988, 3989, 3990, 3991, 3992, 3993, 3994, 3995, 6064,
			6065, 6066, 6067,
			6068,
			6069,
			6070,
			6071,
			6072,
			6073, // Giran
			3999, 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 6077,
			6078, 6079, 6080, 6081,
			6082,
			6083,
			6084,
			6085,
			6086, // Oren
			4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 6090,
			6091, 6092, 6093, 6094, 6095,
			6096,
			6097,
			6098,
			6099, // Aden
			5205, 5206, 5207, 5208, 5209, 5210, 5211, 5212, 5213, 5214, 6105,
			6106, 6107, 6108, 6109, 6110, 6111,
			6112,
			6113,
			6114, // Innadril
			6779, 6780, 6781, 6782, 6783, 6784, 6785, 6786, 6787, 6788, 6792,
			6793, 6794, 6795, 6796, 6797, 6798, 6799,
			6800,
			6801, // Goddard
			7973, 7974, 7975, 7976, 7977, 7978, 7979, 7980, 7981, 7982, 7988,
			7989, 7990, 7991, 7992, 7993, 7994, 7995, 7996,
			7997, // Rune
			7918, 7919, 7920, 7921, 7922, 7923, 7924, 7925, 7926, 7927, 7931,
			7932, 7933, 7934, 7935, 7936, 7937, 7938, 7939, 7940 // Schuttgart
	};

	private static final int[] NPC_IDS = { 35010, 35011,
			35012,
			35013,
			35014,
			35015,
			35016,
			35017,
			35018,
			35019,
			35030,
			35031,
			35032,
			35033,
			35034,
			35035,
			35036,
			35037,
			35038,
			35039, // Gludio
			35010, 35011, 35012, 35013,
			35014,
			35015,
			35016,
			35017,
			35018,
			35019,
			35030,
			35031,
			35032,
			35033,
			35034,
			35035,
			35036,
			35037,
			35038,
			35039, // Dion
			35010, 35011, 35012, 35013, 35014, 35015,
			35016,
			35017,
			35018,
			35019,
			35030,
			35031,
			35032,
			35033,
			35034,
			35035,
			35036,
			35037,
			35038,
			35039, // Giran
			35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017,
			35018,
			35019,
			35030,
			35031,
			35032,
			35033,
			35034,
			35035,
			35036,
			35037,
			35038,
			35039, // Oren
			35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018,
			35019, 35030,
			35031,
			35032,
			35033,
			35034,
			35035,
			35036,
			35037,
			35038,
			35039, // Aden
			35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018,
			35019, 35030, 35031, 35032,
			35033,
			35034,
			35035,
			35036,
			35037,
			35038,
			35039, // Innadril
			35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018,
			35019, 35030, 35031, 35032, 35033, 35034,
			35035,
			35036,
			35037,
			35038,
			35039, // Goddard
			35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018,
			35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036,
			35037,
			35038,
			35039, // Rune
			35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018,
			35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037,
			35038, 35039 // Schuttgart
	};

	// =========================================================
	// Constructor
	public MercTicketManager() {
		System.out.println("Initializing MercTicketManager");
		load();
	}

	// =========================================================
	// Method - Public
	// returns the castleId for the passed ticket item id
	public int getTicketCastleId(int itemId) {
		if (itemId >= ITEM_IDS[0] && itemId <= ITEM_IDS[9]
				|| itemId >= ITEM_IDS[10] && itemId <= ITEM_IDS[19])
			return 1; // Gludio
		if (itemId >= ITEM_IDS[20] && itemId <= ITEM_IDS[29]
				|| itemId >= ITEM_IDS[30] && itemId <= ITEM_IDS[39])
			return 2; // Dion
		if (itemId >= ITEM_IDS[40] && itemId <= ITEM_IDS[49]
				|| itemId >= ITEM_IDS[50] && itemId <= ITEM_IDS[59])
			return 3; // Giran
		if (itemId >= ITEM_IDS[60] && itemId <= ITEM_IDS[69]
				|| itemId >= ITEM_IDS[70] && itemId <= ITEM_IDS[79])
			return 4; // Oren
		if (itemId >= ITEM_IDS[80] && itemId <= ITEM_IDS[89]
				|| itemId >= ITEM_IDS[90] && itemId <= ITEM_IDS[99])
			return 5; // Aden
		if (itemId >= ITEM_IDS[100] && itemId <= ITEM_IDS[109]
				|| itemId >= ITEM_IDS[110] && itemId <= ITEM_IDS[119])
			return 6; // Innadril
		if (itemId >= ITEM_IDS[120] && itemId <= ITEM_IDS[129]
				|| itemId >= ITEM_IDS[130] && itemId <= ITEM_IDS[139])
			return 7; // Goddard
		if (itemId >= ITEM_IDS[140] && itemId <= ITEM_IDS[149]
				|| itemId >= ITEM_IDS[150] && itemId <= ITEM_IDS[159])
			return 8; // Rune
		if (itemId >= ITEM_IDS[160] && itemId <= ITEM_IDS[169]
				|| itemId >= ITEM_IDS[170] && itemId <= ITEM_IDS[179])
			return 9; // Schuttgart
		return -1;
	}

	public void reload() {
		getDroppedTickets().clear();
		load();
	}

	// =========================================================
	// Method - Private
	private final void load() {
		Connection con = null;
		// load merc tickets into the world
		try {
			PreparedStatement statement;
			ResultSet rs;
			con = L2DatabaseFactory.getInstance().getConnection();
			statement = con
					.prepareStatement("SELECT * FROM castle_siege_guards Where isHired = 1");
			rs = statement.executeQuery();
			int npcId;
			int itemId;
			int x, y, z;
			// start index to begin the search for the itemId corresponding
			// to
			// this NPC
			// this will help with:
			// a) skip unnecessary iterations in the search loop
			// b) avoid finding the wrong itemId whenever tickets of
			// different
			// spawn the same npc!
			int startindex = 0;
			while (rs.next()) {
				npcId = rs.getInt("npcId");
				x = rs.getInt("x");
				y = rs.getInt("y");
				z = rs.getInt("z");
				Castle castle = CastleManager.getInstance().getCastle(x, y, z);
				if (castle != null)
					startindex = 20 * (castle.getCastleId() - 1);
				// find the FIRST ticket itemId with spawns the saved NPC in the
				// saved location
				for (int i = startindex; i < NPC_IDS.length; i++)
					if (NPC_IDS[i] == npcId) // Find the index of the item
					// used
					{
						// only handle tickets if a siege is not ongoing in this
						// npc's castle
						if (castle != null
								&& !castle.getSiege().getIsInProgress()) {
							itemId = ITEM_IDS[i];
							// create the ticket in the gameworld
							L2ItemInstance dropticket = new L2ItemInstance(
									IdFactory.getInstance().getNextId(), itemId);
							dropticket
									.setLocation(L2ItemInstance.ItemLocation.INVENTORY);
							dropticket.dropMe(null, x, y, z);
							dropticket.setDropTime(0); // avoids it from beeing
							// removed by the auto
							// item destroyer
							L2World.getInstance().storeObject(dropticket);
							getDroppedTickets().add(dropticket);
						}
						break;
					}
			}
			statement.close();
			System.out.println("Loaded: " + getDroppedTickets().size()
					+ " Mercenary Tickets");
		} catch (Exception e) {
			System.out.println("Exception: loadMercenaryData(): "
					+ e.getMessage());
			e.printStackTrace();
		} finally {
			try {
				con.close();
			} catch (Exception e) {
			}
		}
	}

	// =========================================================
	// Property - Public
	/**
	 * Checks if the passed item has reached the limit of number of dropped
	 * tickets that this SPECIFIC item may have in its castle
	 */
	public boolean isAtTypeLimit(int itemId) {
		int limit = -1;
		// find the max value for this item
		for (int i = 0; i < ITEM_IDS.length; i++)
			if (ITEM_IDS[i] == itemId) // Find the index of the item used
			{
				limit = MAX_MERC_PER_TYPE[i];
				break;
			}
		if (limit <= 0)
			return true;
		int count = 0;
		L2ItemInstance ticket;
		for (int i = 0; i < getDroppedTickets().size(); i++) {
			ticket = getDroppedTickets().get(i);
			if (ticket != null && ticket.getItemId() == itemId)
				count++;
		}
		if (count >= limit)
			return true;
		return false;
	}

	/**
	 * Checks if the passed item belongs to a castle which has reached its limit
	 * of number of dropped tickets.
	 */
	public boolean isAtCasleLimit(int itemId) {
		int castleId = getTicketCastleId(itemId);
		if (castleId <= 0)
			return true;
		int limit = MERCS_MAX_PER_CASTLE[castleId - 1];
		if (limit <= 0)
			return true;
		int count = 0;
		L2ItemInstance ticket;
		for (int i = 0; i < getDroppedTickets().size(); i++) {
			ticket = getDroppedTickets().get(i);
			if (ticket != null
					&& getTicketCastleId(ticket.getItemId()) == castleId)
				count++;
		}
		if (count >= limit)
			return true;
		return false;
	}

	/**
	 * addTicket actions 1) find the npc that needs to be saved in the mercenary
	 * spawns, given this item 2) Use the passed character's location info to
	 * add the spawn 3) create a copy of the item to drop in the world returns
	 * the id of the mercenary npc that was added to the spawn returns -1 if
	 * this fails.
	 */
	public int addTicket(int itemId, L2PcInstance activeChar, String[] messages) {
		int x = activeChar.getX();
		int y = activeChar.getY();
		int z = activeChar.getZ();
		int heading = activeChar.getHeading();
		Castle castle = CastleManager.getInstance().getCastle(activeChar);
		if (castle == null)
			return -1;
		// check if this item can be added here
		for (int i = 0; i < ITEM_IDS.length; i++)
			if (ITEM_IDS[i] == itemId) // Find the index of the item used
			{
				spawnMercenary(NPC_IDS[i], x, y, z, 3000, messages, 0);
				// Hire merc for this caslte. NpcId is at the same index as the
				// item used.
				castle.getSiege().getSiegeGuardManager()
						.hireMerc(x, y, z, heading, NPC_IDS[i]);
				// create the ticket in the gameworld
				L2ItemInstance dropticket = new L2ItemInstance(IdFactory
						.getInstance().getNextId(), itemId);
				dropticket.setLocation(L2ItemInstance.ItemLocation.INVENTORY);
				dropticket.dropMe(null, x, y, z);
				dropticket.setDropTime(0); // avoids it from beeing removed
				// by
				// the auto item destroyer
				L2World.getInstance().storeObject(dropticket); // add to the
				// world
				// and keep track of this ticket in the list
				_droppedTickets.add(dropticket);
				return NPC_IDS[i];
			}
		return -1;
	}

	private void spawnMercenary(int npcId, int x, int y, int z,
			int despawnDelay, String[] messages, int chatDelay) {
		L2NpcTemplate template = NpcTable.getInstance().getTemplate(npcId);
		if (template != null) {
			final L2SiegeGuardInstance npc = new L2SiegeGuardInstance(IdFactory
					.getInstance().getNextId(), template);
			npc.setCurrentHpMp(npc.getMaxHp(), npc.getMaxMp());
			npc.setDecayed(false);
			npc.spawnMe(x, y, (z + 20));
			if (messages != null && messages.length > 0)
				AutoChatHandler.getInstance().registerChat(npc, messages,
						chatDelay);
			if (despawnDelay > 0)
				ThreadPoolManager.getInstance().scheduleGeneral(new Runnable() {
					public void run() {
						npc.deleteMe();
					}
				}, despawnDelay);
		}
	}

	/**
	 * Delete all tickets from a castle; remove the items from the world and
	 * remove references to them from this class
	 */
	public void deleteTickets(int castleId) {
		int i = 0;
		while (i < getDroppedTickets().size()) {
			L2ItemInstance item = getDroppedTickets().get(i);
			if (item != null && getTicketCastleId(item.getItemId()) == castleId) {
				item.decayMe();
				L2World.getInstance().removeObject(item);
				// remove from the list
				getDroppedTickets().remove(i);
			} else
				i++;
		}
	}

	/**
	 * remove a single ticket and its associated spawn from the world (used when
	 * the castle lord picks up a ticket, for example)
	 */
	public void removeTicket(L2ItemInstance item) {
		int itemId = item.getItemId();
		int npcId = -1;
		// find the FIRST ticket itemId with spawns the saved NPC in the saved
		// location
		for (int i = 0; i < ITEM_IDS.length; i++)
			if (ITEM_IDS[i] == itemId) // Find the index of the item used
			{
				npcId = NPC_IDS[i];
				break;
			}
		// find the castle where this item is
		Castle castle = CastleManager.getInstance().getCastleById(
				getTicketCastleId(itemId));
		if (npcId > 0 && castle != null)
			new SiegeGuardManager(castle).removeMerc(npcId, item.getX(),
					item.getY(), item.getZ());
		getDroppedTickets().remove(item);
	}

	public int[] getItemIds() {
		return ITEM_IDS;
	}

	public final List<L2ItemInstance> getDroppedTickets() {
		if (_droppedTickets == null)
			_droppedTickets = new FastList<L2ItemInstance>();
		return _droppedTickets;
	}

	@SuppressWarnings("synthetic-access")
	private static class SingletonHolder {
		protected static final MercTicketManager _instance = new MercTicketManager();
	}
}
